﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using Microsoft.Win32;

using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Folding;

using Babel.SDK;
using System.IO;
using ICSharpCode.AvalonEdit.Editing;

namespace SkGui
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
		private MapFile _mapFile;
        private StringBuilder _warnings;

        public MainWindow()
        {
            InitializeComponent();
			InitializeControls(); 
		}

		private void InitializeControls()
		{
            System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            Version version = assembly.GetName().Version;
            this.Title += " " + version.ToString();
			textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
			CollapseXmlRow();
			gridSplitter.IsEnabled = false;
			buttonDeobfuscate.IsEnabled = false;
		}

		private void OpenMapFile()
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Title = "Open a Babel XML Map File";
			dlg.Multiselect = false;
			dlg.Filter = "Babel Map Files|*.map.xml|All Files|*.*";
			if (dlg.ShowDialog(this).GetValueOrDefault(false))
			{
				AddNewMapFile(dlg.FileName);
			}
		}

		private void AddNewMapFile(string file)
		{
            if (!comboMapFiles.Items.Contains(file))
            {
                int index = comboMapFiles.Items.Add(file);
                comboMapFiles.SelectedIndex = index;
            }
		}

        private void DeobfuscateStackTrace(TextReader sr)
        {
			if (_mapFile == null)
				return;

            StringBuilder stackTrace = new StringBuilder();
            try
            {
				ClearWarnings();
                stackTrace.Append(_mapFile.DeobfuscateStack(sr));

                if ((_warnings != null) && (_warnings.Length > 0))
                {
                    stackTrace.AppendLine(String.Empty);
                    stackTrace.AppendLine("Warnings: ");
                    stackTrace.AppendLine(_warnings.ToString());
                }
            }
            catch (System.Exception e)
            {
                stackTrace.AppendFormat("Error: {0}", e.Message);
            }

            SetStackTraceText(stackTrace.ToString());
        }

		private void ClearWarnings()
		{
            _warnings = new StringBuilder();
		}

        private void ExpandXmlRow()
        {
            double middle = (rowText.ActualHeight + rowXml.ActualHeight) / 2;
            rowXml.Height = new GridLength(middle, GridUnitType.Pixel);            
        }

        private void CollapseXmlRow()
        {
            rowXml.Height = new GridLength(28, GridUnitType.Pixel);
        }

        private void SetStackTraceText(string text)
        {
            textStackTrace.Document.Text = text;
        }

        // If the data object in args is a single file, this method will return the filename.
        // Otherwise, it returns null.
        private string IsSingleFile(DragEventArgs args)
        {
            // Check for files in the hovering data object.
            if (args.Data.GetDataPresent(DataFormats.FileDrop, true))
            {
                string[] fileNames = args.Data.GetData(DataFormats.FileDrop, true) as string[];
                // Check single file or folder.
                if (fileNames.Length == 1)
                {
                    // Check for a file (a directory will return false).
                    if (File.Exists(fileNames[0]))
                    {
                        // At this point we know there is a single file.
                        return fileNames[0];
                    }
                }
            }
            return null;
        }

        #region Settings
        private void LoadSenntings()
        {
            StringCollection recentFiles = Properties.Settings.Default.RecentXmlMapFiles;
            if (recentFiles != null)
            {
                foreach (string file in recentFiles)
                {
                    if (File.Exists(file))
                    {
                        if (!comboMapFiles.Items.Contains(file))
                            comboMapFiles.Items.Add(file);
                    }
                }
            }
        }

        private void SaveSettings()
        {
            StringCollection recentFiles = Properties.Settings.Default.RecentXmlMapFiles;
            if (recentFiles == null)
            {
                recentFiles = new StringCollection();
                Properties.Settings.Default.RecentXmlMapFiles = recentFiles;
            }
            int count = Properties.Settings.Default.RecentXmlMapFilesCount;
            if (count > 0)
            {
                foreach (string item in comboMapFiles.Items)
                {
                    if (!recentFiles.Contains(item))
                    {
                        recentFiles.Add(item);
                        if (recentFiles.Count > count)
                            recentFiles.RemoveAt(0);
                    }
                }
            }
            Properties.Settings.Default.Save();
        }

        #endregion

		private void buttonBrowse_Click(object sender, RoutedEventArgs e)
		{
			OpenMapFile();			
		}

		private void comboMapFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			if (e.AddedItems.Count == 0)
				return;
			string file  = e.AddedItems[0] as string;

			try
			{
                LoadMapFile(file);
			}
			catch (System.Exception ex)
			{
				textEditor.Text = ex.Message;
				if (ex.InnerException != null)
					textEditor.AppendText(Environment.NewLine + ex.InnerException.Message);
			}
		}

        private void LoadMapFile(string fileName)
        {
            grid.IsEnabled = false;
            grid.Opacity = 0.7;
            progressIndicator.IsIndeterminate = true;
            progressIndicator.IsRunning = true;
            bool isError = false;
            string text = String.Empty;

            ThreadPool.QueueUserWorkItem(new WaitCallback(
                (object o) =>
                {
                    try
                    {
                        _mapFile = MapFile.Load(fileName);
						_mapFile.UnresolvedStackFrame += new EventHandler<UnresolvedStackFrameEventArgs>(MapFile_UnresolvedStackFrame);
                        _mapFile.MultipleStackFrameMatch += new EventHandler<MultipleStackFrameMatchEventArgs>(MapFile_MultipleStackFrameMatch);
                        text = _mapFile.Document.ToString();
                    }
                    catch (System.Exception ex)
                    {
                        isError = true;
                        text = ex.Message;
                    }
                    finally
                    {
                        progressIndicator.Dispatcher.Invoke(new Action(
                            () =>
                            {
                                textEditor.Text = text;
                                progressIndicator.IsRunning = false;
                                grid.IsEnabled = true;
                                grid.Opacity = 1;
                                buttonDeobfuscate.IsEnabled = true;

                                if (isError)
                                {
                                    comboMapFiles.Items.Remove(fileName);
                                    expander.IsExpanded = true;
                                }
                            }
                        ), null);                    	
                    }
                }
            ));

        }

		private void MapFile_UnresolvedStackFrame(object sender, UnresolvedStackFrameEventArgs e)
		{
            if (_warnings != null)
            {
                _warnings.Append(String.Format("Warning at line [{0}]: unresolved stack frame: {1}{2}",
                        e.Line, e.StackFrameLine, Environment.NewLine));
            }
		}

        private void MapFile_MultipleStackFrameMatch(object sender, MultipleStackFrameMatchEventArgs e)
        {
            if (_warnings != null)
            {
                _warnings.AppendLine(String.Format("Warning at line [{0}]: multiple methods match the stack frame: {1}",
                        e.Line, e.StackFrameLine));

                for (int i = 0; i < e.Matches.Count; i++)
                {
                    _warnings.AppendLine(String.Format("   {0}: {1}", (i + 1), e.Matches[i]));
                }
            }
        }

		private void buttonDeobfuscate_Click(object sender, RoutedEventArgs e)
		{
            StringReader sr = new StringReader(textStackTrace.Text);
            DeobfuscateStackTrace(sr);
		}

		private void expander_Expanded(object sender, RoutedEventArgs e)
		{
            ExpandXmlRow();
			gridSplitter.IsEnabled = true;
		}

		private void expander_Collapsed(object sender, RoutedEventArgs e)
		{
            CollapseXmlRow();
			gridSplitter.IsEnabled = false;
		}

        private void textStackTrace_PreviewDragOver(object sender, DragEventArgs e)
        {
            // As an arbitrary design decision, we only want to deal with a single file.
            if (IsSingleFile(e) != null) e.Effects = DragDropEffects.Copy;
            else e.Effects = DragDropEffects.None;

            // Mark the event as handled, so TextBox's native DragOver handler is not called.
            e.Handled = true;
        }

        private void textStackTrace_PreviewDrop(object sender, DragEventArgs e)
        {
            // Mark the event as handled, so TextBox's native Drop handler is not called.
            e.Handled = true;

            string fileName = IsSingleFile(e);
            if (fileName == null) return;

			string fileContent = String.Empty;
            using(StreamReader fileToLoad = new StreamReader(fileName))
                fileContent = fileToLoad.ReadToEnd();

            SetStackTraceText(fileContent);
        }

        private void comboMapFiles_PreviewDragOver(object sender, DragEventArgs e)
        {
            // As an arbitrary design decision, we only want to deal with a single file.
            if (IsSingleFile(e) != null) e.Effects = DragDropEffects.Copy;
            else e.Effects = DragDropEffects.None;

            // Mark the event as handled, so TextBox's native DragOver handler is not called.
            e.Handled = true;
        }

        private void comboMapFiles_PreviewDrop(object sender, DragEventArgs e)
        {
            // Mark the event as handled, so TextBox's native Drop handler is not called.
            e.Handled = true;

            string fileName = IsSingleFile(e);
            if (fileName == null) return;

            AddNewMapFile(fileName);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            LoadSenntings();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            SaveSettings();
        }
		
        private void textMessage_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
			textMessage.Visibility = Visibility.Collapsed;
        }

        private void textMessage_PreviewDragOver(object sender, System.Windows.DragEventArgs e)
        {
			textMessage.Visibility = Visibility.Collapsed;
		}

		private void textMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
		{
			
		}

		private void gridSplitter_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			expander.IsExpanded = !expander.IsExpanded;
		}
    }
}
